import java.awt.event.*;
import java.awt.*;
import java.util.*;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;


public class VariableControl extends Panel implements CrispVariableBox, Tickable {   
    // inputs
    private java.util.List<Double> values;
    private static final int TEXT_INPUT_COLS = 10;

    public static final double MAX = 20;
    public static final double MIN = 1;

    // controls
    private java.util.List<TextField> controlList;

    private FuzzySetsManager manager;

    private enum InputTypes {
        InitialVelocity, InitialDistance1, InitialDistance2, DesiredVelocity, DesiredDistance
    };
    private double[] DEFAULT_INPUTS = {MIN, MAX, MAX, MAX/2, MAX/2};
    
    public VariableControl (String label)   {     
        assert DEFAULT_INPUTS.length == InputTypes.values().length : "Must keep these in sync";
        this.setLayout( new FlowLayout() );

        createValues();
        createInputs();
        setDefaults();
        this.setPreferredSize( determinePreferredSize() );
        this.setMaximumSize( determinePreferredSize() );

        manager = FuzzySetsManager.getInstance();
        manager.updateDesiredParameters(getDesiredDistance(), getDesiredVelocity());
    } 

    /**
     * Attempt to get a good size for the panel.
     * TODO: This doesn't really work. Another method?
     * Maybe something to do with minumum too?
     */
    private Dimension determinePreferredSize() {
        // just make up a size
        final int N_PER_LINE = 3;
        int sumY = 0;
        for ( TextField c : controlList ) {
            sumY += 50;
        }
        return new Dimension( TEXT_INPUT_COLS*25*N_PER_LINE, sumY/N_PER_LINE );
    }

    /**
     * Get the last _cached_ value for desired values.
     * Grab these out of of our array of values
     * TODO: Consider changing this to a map.
     */
    public double getDesiredVelocity() {
        return values.get( InputTypes.DesiredVelocity.ordinal() );
    }
    public double getDesiredDistance() {
        return values.get( InputTypes.DesiredDistance.ordinal() );
    }
    public double getInitialVelocity() {
        return values.get( InputTypes.InitialVelocity.ordinal() );
    }
    public double getInitialDistance() {
        return values.get( InputTypes.InitialDistance1.ordinal() );
    }
    public double getInitialDistance(int i) {
        if ( i == 0 ) {
            return getInitialDistance();
        }
        else {
            return values.get( InputTypes.InitialDistance2.ordinal() );
        }
    }

    /**
     * Create the list of values that CrispVariableBox encapsulates.
     */
    private void createValues() {
        // create a value for each InputType
        values = new ArrayList<Double>( InputTypes.values().length );
        for (int i = 0; i < InputTypes.values().length; ++i) {
            values.add(DEFAULT_INPUTS[i]);
        }
    }
    /** Create the list of controls.
     */
    private void createInputs() {
        controlList = new ArrayList<TextField>( InputTypes.values().length );
        for( InputTypes iName : InputTypes.values() ) {
            this.add( makeInput( iName ) );
        }
    }
    private Panel makeInput(InputTypes iName) {
        Panel p = new Panel ( new FlowLayout() ); 

        Label name = new Label( iName.toString() ); 

        TextField inputControl = new TextField(""+MIN, TEXT_INPUT_COLS);
        inputControl.addFocusListener( new ValueChanged(iName) );
        controlList.add( inputControl );  // keep a handle to it

        p.add( name );
        p.add( inputControl );
        return p;
    }

    private void setDefaults() {
        // use a loop to set our initials
        for( InputTypes iName : InputTypes.values() ) {
            TextField t = controlList.get( iName.ordinal() );
            t.setText(""+ DEFAULT_INPUTS[ iName.ordinal() ]);
        }
    }

    public void paint(Graphics g)
    {
    	g.setColor(Color.BLACK);
    	g.drawLine(0, 0, this.getWidth(), 0);
    	g.drawLine(0, this.getHeight()-1, this.getWidth(), this.getHeight()-1); 	
    	
        // draw
//        g.setColor(Color.black);
//        g.drawString("Fuzzy Control Panel", 20, 20);
    }

    public void tick() {

        // update graphics (necessary?)
        //this.repaint();
    }
    private double getValueAt( InputTypes iName ) {
        TextField ctrl = controlList.get( iName.ordinal() );

        double i = 0.0;
        try {
            i = Double.parseDouble( ctrl.getText() );
        }
        catch (NumberFormatException ex) {
            i = MIN;
            System.err.println("Invalid number found!");
            ctrl.setText(""+ i);
        }

        // bound value

        return enforceBounds(i, ctrl);
    }

    private double enforceBounds(double val, TextField ctrl) {
        double i = val;
        
        i = Math.max(MIN, i);
        i = Math.min(MAX, i);

        if ( i != val ) {
            ctrl.setText(""+i);
        }

        assert i <= MAX : "check bound";
        assert i >= MIN : "check bound";

        return i;
    }

    private void updateListeners() {
        // get all listeners
        Object o = new Object (); 
        for ( PropertyChangeListener listener : this.getPropertyChangeListeners() ) {
            listener.propertyChange(null);
        }
    }

    private class ValueChanged extends FocusAdapter {
      private InputTypes type;
      public ValueChanged( InputTypes type ) {
          this.type = type;
      }
      public void focusLost(FocusEvent e) {
          values.set( type.ordinal(), getValueAt(type) );
          manager.updateDesiredParameters(getDesiredDistance(), getDesiredVelocity());
          System.out.println("update: "+ type +"="+ getValueAt(type));

          // update 
          updateListeners();
      }
    }
}
